﻿/// <reference path="../../../typings/requirejs/require.d.ts" />
/// <reference path="ModuleLoaderConfig.ts" />

import Module = Sustainalytics.Module;


export function get_browser_info(): any {
    var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if (/trident/i.test(M[1])) {
        tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
        return 'IE ' + (tem[1] || '');
    }
    if (M[1] === 'Chrome') {
        tem = ua.match(/\bOPR\/(\d+)/)
        if (tem != null) { return 'Opera ' + tem[1]; }
    }
    M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
    if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
    return {
        browser: M[0],
        version: M[1]
    };
}



// requireJS loader plugin - load() function for loading required resource
export function load(name: string, parentRequire, onload, config): void {

    var startDate = new Date();

    if (!Module.ModuleLoaderConfig.modules) {
        throw 'Module loader was not initialized. Set modules using ModuleLoaderConfig.modules.';
    }

    // get loader
    var loader: Loader = new Loader(Module.ModuleLoaderConfig.modules, reportProgress);

    // get file count for module
    var fileCount = loader.getFileCountByName(name);

    var loadedFileCount: number = 0;

    // add to total count
    Module.ModuleLoaderConfig.addTotalCount(fileCount);

    // load module dependencies
    loader.loadModuleName(name,
        // success
        function () {
            // remove from total count
            Module.ModuleLoaderConfig.removeTotalCount(loadedFileCount, fileCount);

            tryLogErrors(loader);

            onload(null);
        },
        // error
        function () {
            // remove from total count
            Module.ModuleLoaderConfig.removeTotalCount(loadedFileCount, fileCount);

            tryLogErrors(loader);

            onload.error.apply(onload, arguments);
        });


    function tryLogErrors(loader) {

        var endDate = new Date();
        var duration = endDate.getTime() - startDate.getTime();

        var files: string = loader.errLoadingFiles;

        if (files && files.length > 0) {
            console.error('MODULE LOADER [' + duration + 'ms] :: Error loading: ', files);
        }
        else {
            console.info('MODULE LOADER [' + duration + 'ms] :: [' + name + '] using ' + loader.instanceLoadedFileCount + ' additional files from total of ' + (Loader.loadedFileIndex + 1) + ' files.', { files: loader.instanceLoadedFiles });
            //console.log(loader.instanceLoadedFiles);
        }
    };

    function reportProgress(count: number) {
        loadedFileCount += count;
        Module.ModuleLoaderConfig.addCount(count);
    }
}


// Module Loader for loading modules
export class Loader {
    
    public static loadedModules: any = {};
    public static loadedFiles: any = {};
    public static loadedFilesArray: string[] = [];
    public static loadedFileIndex: number = -1;
    public static errLoadingFiles: string[] = [];    

    private modules: any;
    public instanceLoadedFiles: string[] = [];
    public instanceLoadedFileCount: number = 0;

    private progressFunc: (count: number) => void;

    constructor(modules: any, progressFunc?: (count: number) => void) {
        var ctx = this;

        ctx.modules = modules;
        ctx.progressFunc = progressFunc;
    } 

    private reportProgress(count: number) {
        var ctx = this;

        if (ctx.progressFunc) {
            ctx.progressFunc(count);
        }
    }

    public loadModuleName(moduleName: string, onSuccess?: any, onError?: any) {
        var ctx = this;

        var mod = <Module.Module>ctx.modules[moduleName];

        if (!mod) {
            console.error('Module \'' + moduleName + '\' does not exist.');
            return;
        }

        ctx.loadModule(moduleName, mod, onSuccess, onError);
    }

    public loadModule(name: string, mod: Module.Module, onSuccess?: any, onError?: any) {
        var ctx = this;

        if (Loader.loadedModules[name]) {

            // notify that all module files were loaded
            var count = ctx.getFileCount(mod);
            ctx.reportProgress(count);

            if (onSuccess)
                onSuccess();

            return;
        }

        // load module requirements
        ctx.loadModuleRequire(mod.require,
            // success
            function () {
                ctx.loadModuleFiles(mod.files,
                    // success
                    function () {
                        Loader.loadedModules[name] = true;

                        if (onSuccess)
                            onSuccess();
                    },
                    // err
                    function () {
                        if (onError)
                            onError();
                    });
            },
            // err
            function () {
                ctx.loadModuleFiles(mod.files,
                    // success
                    function () {
                        if (onSuccess)
                            onSuccess();
                    },
                    // err
                    function () {
                        if (onError)
                            onError();
                    });
            });
    }

    public loadModuleRequire(requireModules: string[], onSuccess?: any, onError?: any) {
        var ctx = this;

        if (!requireModules || requireModules.length == 0) {
            if (onSuccess)
                onSuccess();
            return;
        }

        var isErr = false;
        var requireModulesCount = requireModules.length;

        function tryCallSuccess() {
            if (requireModulesCount == 0) {
                if (!isErr) {
                    if (onSuccess)
                        onSuccess();
                }
                else {
                    if (onError)
                        onError();
                }
            }
        };

        for (var index = 0; index < requireModules.length; index++)
        {
            var reqModName = requireModules[index];

            var reqMod = ctx.modules[reqModName];

            ctx.loadModule(reqModName, reqMod,
                // success
                function () {
                    requireModulesCount--;
                    tryCallSuccess();
                },
                // err
                function () {
                    requireModulesCount--;
                    isErr = true;
                    tryCallSuccess();
                });
        }
    }

    public loadModuleFiles(moduleFiles: string[], onSuccess?: any, onError?: any) {
        var ctx = this;

        if (!moduleFiles || moduleFiles.length == 0) {
            if (onSuccess)
                onSuccess();
            return;
        }

        moduleFiles = moduleFiles.slice(0);

        var isErr = false;

        function loadNextModuleFile() {

            if (moduleFiles.length > 0) {
                var moduleFile = moduleFiles.shift();

                if (!moduleFile) {
                    loadNextModuleFile();
                    return;
                }

                ctx.loadModuleFile(moduleFile,
                    // success
                    function () {
                        loadNextModuleFile();
                    },
                    // err
                    function () {
                        loadNextModuleFile();
                        isErr = true;
                    });
            }
            else {
                if (!isErr) {
                    if (onSuccess)
                        onSuccess();
                }
                else {
                    if (onError) {
                        onError();
                    }
                }
            }
        }

        loadNextModuleFile();
    }

    public loadModuleFile(moduleFileObj: any, onSuccess?: any, onError?: any) {
        var ctx = this;

        var moduleFile: string = null;
        var browserList: any[] = null;

        // get module file and browser spec
        if (typeof (moduleFileObj) == 'string') {
            moduleFile = moduleFileObj;
        }
        else {
            moduleFile = moduleFileObj.path;
            browserList = moduleFileObj.browser;
        }


        if (Loader.loadedFiles[moduleFile]) {
            if (onSuccess)
                onSuccess();
            return;
        }

        function moduleFileProcessed() {
            if (!Loader.loadedFiles[moduleFile]) {
                Loader.loadedFileIndex++;
                Loader.loadedFiles[moduleFile] = Loader.loadedFileIndex;
                Loader.loadedFilesArray[Loader.loadedFileIndex] = moduleFile;

                ctx.instanceLoadedFiles.push(moduleFile);
                ctx.instanceLoadedFileCount++;                
            }

            ctx.reportProgress(1);
        }


        // if browser spec exists then check browser
        if (browserList) {

            // get browser info
            var browserInfo = get_browser_info();

            // determine if current browser has a match
            var isMatch = false;

            for (var index = 0; index < browserList.length; index++) {

                var browser = browserList[index];

                if (browserInfo.browser === browser.name) {
                    if (browser.vers && browserInfo.version) {

                        var currentVers = ('00000000000' + browserInfo.version.toString()).slice(-10);
                        var reqVers = ('00000000000' + browser.vers.toString()).slice(-10);

                        if (browser.comp) {
                            switch (browser.comp) {
                                case 'lte':
                                    isMatch = currentVers <= reqVers;
                                    break;
                                case 'lt':
                                    isMatch = currentVers < reqVers;
                                    break;
                                case 'gte':
                                    isMatch = currentVers >= reqVers;
                                    break;
                                case 'gt':
                                    isMatch = currentVers > reqVers;
                                    break;
                            }
                        }
                        else {
                            isMatch = currentVers == reqVers;
                        }
                    }
                    else {
                        isMatch = true;
                    }
                }

                if (isMatch) {
                    break;
                }
            }

            // if file is not a match then mark the file as processed
            if (!isMatch) {
                moduleFileProcessed();
                if (onSuccess)
                    onSuccess();
                return;
            }
        }


        require([moduleFile],
            // success
            function () {
                moduleFileProcessed();

                if (onSuccess)
                    onSuccess();
            },
            // err
            function () {
                moduleFileProcessed();

                Loader.errLoadingFiles.push(moduleFile);

                if (onError)
                    onError();
            });
    }


    public getFileCountByName(moduleName: string): number {
        var ctx = this;

        var mod = <Module.Module>ctx.modules[moduleName];

        if (!mod) {
            console.error('Module \'' + moduleName + '\' does not exist.');
            return;
        }

        var count = ctx.getFileCount(mod);

        return count;
    }

    public getFileCount(mod: Module.Module): number {
        var ctx = this;

        var localCount: number = 0;

        if (mod.require && mod.require.length > 0) {

            for (var index = 0; index < mod.require.length; index++) {
                var reqModName = mod.require[index];
                var reqModCount = ctx.getFileCountByName(reqModName);
                localCount += reqModCount;
            }
        }

        if (mod.files && mod.files.length) {
            localCount += mod.files.length;
        }

        return localCount;
    }
} 